package com.leyou.goods.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.leyou.goods.item.pojo.*;
import com.leyou.goods.search.client.BrandClient;
import com.leyou.goods.search.client.CategoryClient;
import com.leyou.goods.search.client.GoodsClient;
import com.leyou.goods.search.client.SpecificationClient;
import com.leyou.goods.search.pojo.Goods;
import com.leyou.goods.search.pojo.SearchRequest;
import com.leyou.goods.search.pojo.SearchResult;
import com.leyou.goods.search.reponsitory.GoodsRepository;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
public class SearchService {


    @Autowired
    BrandClient brandClient;
    @Autowired
    CategoryClient categoryClient;
    @Autowired
    GoodsClient goodsClient;
    @Autowired
    SpecificationClient specClient;

    @Autowired
    GoodsRepository goodsRepository;

    // jackson
    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * 完成从spu对象，转变为Goods对象的一个过程
     * @param
     * @return
     * @throws Exception
     */
    public Goods buildGoods(Spu s) throws Exception {

        Brand brand = brandClient.queryBrandById(s.getBrandId());


        List<String> categoryNames = categoryClient.queryNameByIds(Arrays.asList(s.getCid1(), s.getCid2(), s.getCid3()));


        // 价格的获取，价格是属于sku   先通过spuid，查询对应的sku的集合
        List<Sku> skus = goodsClient.querySkuBySpuId(s.getId());
        List<Long> prices= new ArrayList<>();
        // 为了节省时间和空间，实体类中的有些数据，没有用！
        List<Map<String,Object>> skuMapList = new ArrayList<>();
        skus.forEach(sku->{
            // 把讲个准备好！
            prices.add(sku.getPrice());
            Map<String,Object> map = new HashMap<>();
            map.put("id",sku.getId());
            map.put("title",sku.getTitle());
            map.put("price",sku.getPrice());

            String images = sku.getImages();
            String image = "";
            if(StringUtils.isNotBlank(images)){
                image = StringUtils.split(images,",")[0];
            }

            map.put("image",image);

            skuMapList.add(map);
        });

        Map<String, Object> specs = new HashMap<>();
        // 查询到所有的规格参数  key
        List<SpecParam> params = specClient.queryParams(null, s.getCid3(), true, null);
        // 得到当前spuDetail的对象
        SpuDetail spuDetail = goodsClient.querySpuDetailById(s.getId());
        // 用来存放spu属性
        String genericSpec = spuDetail.getGenericSpec();
        // 用来存放sku属性
        String specialSpec = spuDetail.getSpecialSpec();
        // String字符串不方便操作，将json字符串，转化为java中能用的对象即可
        // 反序列化，将json字符串，转变为Java中的对象
        Map<Long,Object> genericSpecMap = MAPPER.readValue(genericSpec,
                new TypeReference<Map<Long,Object>>(){
                });
        Map<Long,List<Object>> specialSpecMap = MAPPER.readValue(specialSpec,
                new TypeReference<Map<Long,List<Object>>>(){
                });


        params.forEach(param->{
            // spu，通用规格参数
            if(param.getGeneric()){
                // 通过规格参数的key的id，得到这个value值
                String value = genericSpecMap.get(param.getId()).toString();
                // 如果当前的值，是属于数字，我们需要额外的进行判断
                if(param.getNumeric()){
                    value = chooseSegment(value,param);
                }
                specs.put(param.getName(),value);
            }
            // sku的属性
            else{
                List<Object> ssList = specialSpecMap.get(param.getId());
                specs.put(param.getName(),ssList);
            }
        });

        Goods goods = new Goods();
        goods.setId(s.getId());//spuId
        // title+品牌+分类
        goods.setAll(s.getTitle()+" "+
                brand.getName()+" "+
                StringUtils.join(categoryNames, " "));
        goods.setBrandId(s.getBrandId());
        goods.setSubTitle(s.getSubTitle());
        goods.setCid1(s.getCid1());
        goods.setCid2(s.getCid2());
        goods.setCid3(s.getCid3());
        goods.setCreateTime(s.getCreateTime());

        // price价格，价格是属于sku  得到这个spu下的所有的sku对象
        goods.setPrice(prices);
        // 要的是一个json格式，Jackson的使用，将list集合转变为json格式
        // skuMapList
        goods.setSkus(MAPPER.writeValueAsString(skuMapList));

        goods.setSpecs(specs);

        return goods;
    }
    // 如果是数字类型，就将这个数字，转化为它所在的一个范围即可
    private String chooseSegment(String value, SpecParam p) {
        // float  单精度  double 双精度
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if(segs.length == 2){
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if(val >= begin && val < end){
                if(segs.length == 1){
                    result = segs[0] + p.getUnit() + "以上";
                }else if(begin == 0){
                    result = segs[1] + p.getUnit() + "以下";
                }else{
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    public SearchResult search(SearchRequest searchRequest) {
        // 1.跟我们的elasticsearch关联起来
        String key = searchRequest.getKey();

        // 判断是否有传值过来
        if(StringUtils.isEmpty(key)){
            return null;
        }
        // 2.有哪些条件需要处理
        // 1）对key进行，全文的索引查询  match
        // 2）分页  Pageable
        // 3）根据cid3，进行聚合
        // 4）根据brandId，进行聚合
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        // 如果用户传的是小米手机  == 分词之后  小米 and 手机
        QueryBuilder myQuery = QueryBuilders.matchQuery("all", key).operator(Operator.AND);

        // 创建bool查询的对象
        BoolQueryBuilder boolQueryBuilder = buildBooleanQueryBuilder(myQuery,searchRequest);


        queryBuilder.withQuery(boolQueryBuilder);

        // 结果过滤，优化速度sdfadf    word
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","skus","subTitle"},null));

        int page = searchRequest.getPage();
        int size = searchRequest.getSize();
        queryBuilder.withPageable(PageRequest.of(page-1,size));

        // 先根据分类cid3进行聚合，先搜索出结果，在去聚合
        queryBuilder.addAggregation(AggregationBuilders.terms("categories").field("cid3"));
        queryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brandId"));


        // 将条件查询进行之后，得到的分页对象
        // 只是分页相关，聚合相关？
        AggregatedPage<Goods> pageInfo = (AggregatedPage<Goods>)goodsRepository.search(queryBuilder.build());

//        PageResult<Goods> pageResult = new PageResult<>();
//        pageResult.setTotal(pageInfo.getTotalElements());
//        pageResult.setItems(pageInfo.getContent());
//        pageResult.setTotalPage(pageInfo.getTotalPages());

        // 对聚合对象，进行解析
        List<Map<String,Object>> categories = getCategoryAggResult(pageInfo.getAggregation("categories"));
        List<Brand> brands = getBrandAggResult(pageInfo.getAggregation("brands"));

        // 分类有可能存在很多个
        // 分类的，因为我们需要根据分类的id，去求对应的规格参数都有哪些
        // 这次的聚合，必须得根据词条查询之后，才能接着聚合
        List<Map<String,Object>> specs = getParamAggResult((Long)categories.get(0).get("id"),myQuery);

        SearchResult searchResult = new SearchResult();
        searchResult.setTotal(pageInfo.getTotalElements());
        searchResult.setItems(pageInfo.getContent());
        searchResult.setTotalPage( pageInfo.getTotalPages());
        searchResult.setCategories(categories);
        searchResult.setBrands(brands);
        searchResult.setSpecs(specs);

        return searchResult;
    }

    private BoolQueryBuilder buildBooleanQueryBuilder(QueryBuilder myQuery, SearchRequest searchRequest) {
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 在bool查询中，添加根据key值的匹配查询
        boolQueryBuilder.must(myQuery);

        Map<String, String> filter = searchRequest.getFilter();

        filter.entrySet().forEach(entity->{
            String key = entity.getKey();
            if(key.equals("品牌")){
                key = "brandId";
            }else if(key.equals("分类")){
                key = "cid3";
            }else{
                key ="specs."+key+".keyword";
            }
            String value = entity.getValue();

            boolQueryBuilder.filter(QueryBuilders.termQuery(key,value));
        });

        return boolQueryBuilder;
    }

    private List<Map<String, Object>> getParamAggResult(Long cid3, QueryBuilder myQuery) {

        // 全新的聚合查询
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(myQuery);

        // 得到当前分类下的，所有用于搜索的规格参数对象
        List<SpecParam> params = specClient.queryParams(null, cid3, true, null);

        // 通过规格参数的名字进行聚合
        params.forEach(p->{
            queryBuilder.addAggregation
                    (AggregationBuilders.terms(p.getName())
                            .field("specs."+p.getName()+".keyword"));
        });
        // 纯粹的聚合，不包含任何字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null));
        // 执行搜索
        AggregatedPage<Goods> goodsPage = (AggregatedPage<Goods>)goodsRepository.search(queryBuilder.build());

        // 装载规格参数的集合
        // Map集合中的key是什么？
        List<Map<String,Object>> paramMapList = new ArrayList<>();
        // 得到所有的聚合的结果
        // String泛型，表示的是给这个聚合起的名字
        Map<String, Aggregation> stringAggregationMap = goodsPage.getAggregations().asMap();
        // map集合的遍历  得到key的集合，速度比较慢
//        Set<String> strings = stringAggregationMap.keySet();
        Set<Map.Entry<String, Aggregation>> entries = stringAggregationMap.entrySet();
        for (Map.Entry<String, Aggregation> entry : entries) {
            Map<String,Object> map = new HashMap<>();
            map.put("key",entry.getKey());

            List<Object> options=new ArrayList<>();
            StringTerms terms = (StringTerms)entry.getValue();
            terms.getBuckets().forEach(b->{
                options.add(b.getKeyAsString());
            });
            map.put("options",options);
            paramMapList.add(map);
        }

        return paramMapList;
    }

    private List<Brand> getBrandAggResult(Aggregation brands) {
        LongTerms terms = (LongTerms)brands;
        List<LongTerms.Bucket> buckets = terms.getBuckets();

        List<Brand> list = new ArrayList<>();
        buckets.forEach(b->{
            long l = b.getKeyAsNumber().longValue();
            // 得到了brand对象id，根据得到这个对象的其他信息
            Brand brand = brandClient.queryBrandById(l);
            list.add(brand);
        });

        return list;

    }

    private List<Map<String, Object>> getCategoryAggResult(Aggregation aggregation) {
        // 解析
        LongTerms terms = (LongTerms)aggregation;

        List<LongTerms.Bucket> buckets = terms.getBuckets();
        List<Long> cids = new ArrayList<>();
        buckets.forEach(b->{
            long l = b.getKeyAsNumber().longValue();
            cids.add(l);
        });
        // 将得到的聚合的ids，并将其转变为names
        List<String> names = categoryClient.queryNameByIds(cids);
        System.out.println(names.toString());
        List<Map<String, Object>> list = new ArrayList<>();

        for (int i = 0 ; i <cids.size();i++){
            Map<String,Object> map = new HashMap<>();
            map.put("id",cids.get(i));
            map.put("name",names.get(i));
            list.add(map);
        }
        return list;

    }

    //


}
